/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.huawei.healthecology.operator;

import com.huawei.healthecology.data.ble.data.BluetoothEventAction;
import com.huawei.healthecology.data.ble.request.BleConnectionPriorityRequest;
import com.huawei.healthecology.data.ble.request.BleDeviceCharacteristicGetRequest;
import com.huawei.healthecology.data.ble.request.BleDeviceConnectRequest;
import com.huawei.healthecology.data.ble.request.BleDeviceConnectionCloseRequest;
import com.huawei.healthecology.data.ble.request.BleDeviceDiscoverRequest;
import com.huawei.healthecology.data.ble.request.BleMtuSetRequest;
import com.huawei.healthecology.data.ble.request.CharacteristicNotifyRequest;
import com.huawei.healthecology.data.ble.request.CharacteristicReadRequest;
import com.huawei.healthecology.data.ble.request.CharacteristicWriteRequest;
import com.huawei.healthecology.data.ble.request.DescriptorReadRequest;
import com.huawei.healthecology.data.ble.request.DescriptorWriteRequest;
import com.huawei.healthecology.data.ble.response.BleResponseCode;
import com.huawei.healthecology.data.bluetooth.response.BluetoothAdapterStateResponse;
import com.huawei.healthecology.data.bluetooth.response.BluetoothResponseCode;
import com.huawei.healthecology.data.http.HttpResponseCode;
import com.huawei.healthecology.data.http.KitHttpRequest;
import com.huawei.healthecology.data.platform.PlatformOperation;
import com.huawei.healthecology.data.platform.PlatformRequest;
import com.huawei.healthecology.data.platform.PlatformResponse;
import com.huawei.healthecology.module.ClientModule;
import com.huawei.healthecology.processor.BleCentralProcessor;
import com.huawei.healthecology.processor.WifiDeviceProcessor;

import ohos.bluetooth.BluetoothHost;

import java.util.Arrays;
import java.util.Optional;

/**
 * The Platform operator code, include Ble, Bt, Wifi operations.
 */
public enum PlatformOperationCode {
    OPEN_BLUETOOTH_ADAPTER(1_001) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            return ClientModule.getBleCentralProcessor()
                .map(BleCentralProcessor::openBluetoothAdapter)
                .map(responseCode -> PlatformResponse.builder()
                    .responseCode(responseCode).build());
        }
    },

    CLOSE_BLUETOOTH_ADAPTER(1_002) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            return ClientModule.getBleCentralProcessor()
                .map(BleCentralProcessor::closeBluetoothAdapter)
                .map(responseCode -> PlatformResponse.builder()
                    .responseCode(responseCode)
                    .build());
        }
    },

    GET_BLUETOOTH_ADAPTER_STATE(1_003) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            return ClientModule.getBleCentralProcessor()
                .map(BleCentralProcessor::getBluetoothAdapterState)
                .map(stateResponse -> PlatformResponse.builder()
                    .responseValue(stateResponse)
                    .build());
        }
    },

    ON_BLUETOOTH_ADAPTER_STATE_CHANGE(1_004) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            return ClientModule.getBleCentralProcessor()
                .map(processor -> processor.onBluetoothAdapterStateChange(state -> {
                    BluetoothAdapterStateResponse response = BluetoothAdapterStateResponse.builder()
                        .isDiscovering(state.getEventAction().equals(BluetoothEventAction.DISCOVERY_STARTED))
                        .isAvailable(state.getCurrentState() == BluetoothHost.STATE_ON)
                        .responseCode(BluetoothResponseCode.OPERATION_SUCCESS)
                        .build();
                    ClientModule.getParser()
                        .flatMap(parser -> parser.stringify(response))
                        .ifPresent(result -> operation.getCallbackOperator().onResponse(result));
                    }))
                .map(resultCode -> PlatformResponse.builder()
                    .responseCode(resultCode)
                    .build());
        }
    },

    START_BLE_DEVICES_DISCOVERY(1_005) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            return ClientModule.getBleCentralProcessor()
                .map(processor -> operation.getRawRequest()
                    .flatMap(rawRequest -> ClientModule.getParser()
                        .flatMap(parser -> parser.mapTo(rawRequest, BleDeviceDiscoverRequest.class))
                        .map(processor::startBleDevicesDiscovery)
                        .map(responseCode -> PlatformResponse.builder()
                            .responseCode(responseCode)
                            .build()))
                    .orElse(PlatformResponse.builder()
                        .responseCode(BleResponseCode.OPERATION_FAILED)
                        .build()));
        }
    },

    STOP_BLE_DEVICES_DISCOVERY(1_006) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            return ClientModule.getBleCentralProcessor()
                .map(BleCentralProcessor::stopBleDevicesDiscovery)
                .map(responseCode -> PlatformResponse.builder()
                    .responseCode(responseCode)
                    .build());
        }
    },

    ON_BLE_DEVICE_FOUND(1_007) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            ClientModule.getBleCentralProcessor().ifPresent(processor ->
                processor.onBleDevicesFound(deviceDataList ->
                    ClientModule.getParser()
                        .flatMap(parser -> parser.stringify(deviceDataList))
                        .ifPresent(result -> operation.getCallbackOperator().onResponse(result))));
            return Optional.of(PlatformResponse.builder()
                .responseCode(BleResponseCode.OPERATION_SUCCESS)
                .build());
        }
    },

    CREATE_BLE_CONNECTION(1_008) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            return ClientModule.getGattProcessor()
                .map(processor -> operation.getRawRequest()
                    .flatMap(rawRequest -> ClientModule.getParser()
                        .flatMap(parser -> parser.mapTo(rawRequest, BleDeviceConnectRequest.class)))
                    .map(processor::createBleConnection)
                    .map(responseCode -> PlatformResponse.builder()
                        .responseCode(responseCode)
                        .build())
                    .orElse(PlatformResponse.builder()
                        .responseCode(BleResponseCode.OPERATION_FAILED)
                        .build()));
        }
    },

    CLOSE_BLE_CONNECTION(1_009) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            return ClientModule.getGattProcessor()
                .map(processor -> operation.getRawRequest()
                    .flatMap(rawRequest -> ClientModule.getParser()
                        .flatMap(parser -> parser.mapTo(rawRequest, BleDeviceConnectionCloseRequest.class)))
                    .map(processor::closeBleConnection)
                    .map(responseCode -> PlatformResponse.builder()
                        .responseCode(responseCode)
                        .build())
                    .orElse(PlatformResponse.builder()
                        .responseCode(BleResponseCode.OPERATION_FAILED)
                        .build()));
        }
    },

    ON_BLE_CONNECTION_CHANGE(1_010) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            ClientModule.getGattProcessor().ifPresent(processor ->
                processor.onBleConnectionChange(connectionState ->
                    ClientModule.getParser()
                        .flatMap(parser -> parser.stringify(connectionState))
                        .ifPresent(result -> operation.getCallbackOperator().onResponse(result))));
            return Optional.of(PlatformResponse.builder()
                .responseCode(BleResponseCode.OPERATION_SUCCESS)
                .build());
        }
    },

    ON_BLE_SERVICE_DISCOVERED(1_011) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            ClientModule.getGattProcessor().ifPresent(processor ->
                processor.onBleServiceDiscovered(discoveryResult ->
                    ClientModule.getParser()
                        .flatMap(parser -> parser.stringify(discoveryResult))
                        .ifPresent(result -> operation.getCallbackOperator().onResponse(result))));
            return Optional.of(PlatformResponse.builder()
                .responseCode(BleResponseCode.OPERATION_SUCCESS)
                .build());
        }
    },

    ON_BLE_MTU_UPDATED(1_012) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            ClientModule.getGattProcessor().ifPresent(processor ->
                processor.onBleMtuUpdated(updateData ->
                    ClientModule.getParser()
                        .flatMap(parser -> parser.stringify(updateData))
                        .ifPresent(result -> operation.getCallbackOperator().onResponse(result))));
            return Optional.of(PlatformResponse.builder()
                .responseCode(BleResponseCode.OPERATION_SUCCESS)
                .build());
        }
    },

    READ_BLE_CHARACTERISTIC_VALUE(1_013) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            return ClientModule.getGattProcessor()
                .map(processor -> operation.getRawRequest()
                    .flatMap(rawRequest -> ClientModule.getParser()
                        .flatMap(parser -> parser.mapTo(rawRequest, CharacteristicReadRequest.class)))
                    .map(processor::readBleCharacteristicValue)
                    .map(responseCode -> PlatformResponse.builder()
                        .responseCode(responseCode)
                        .build())
                    .orElse(PlatformResponse.builder()
                        .responseCode(BleResponseCode.OPERATION_FAILED)
                        .build()));
        }
    },

    WRITE_BLE_CHARACTERISTIC_VALUE(1_014) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            return ClientModule.getGattProcessor()
                .map(processor -> operation.getRawRequest()
                    .flatMap(rawRequest -> ClientModule.getParser()
                        .flatMap(parser -> parser.mapTo(rawRequest, CharacteristicWriteRequest.class)))
                    .map(processor::writeBleCharacteristicValue)
                    .map(resultCode -> PlatformResponse.builder()
                        .responseCode(resultCode)
                        .build())
                    .orElse(PlatformResponse.builder()
                        .responseCode(BleResponseCode.OPERATION_FAILED)
                        .build()));
        }
    },

    ON_BLE_CHARACTERISTIC_VALUE_CHANGE(1_015) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            ClientModule.getGattProcessor().ifPresent(processor ->
                processor.onBleCharacteristicValueChange(changeResponse ->
                    ClientModule.getParser()
                        .flatMap(parser -> parser.stringify(changeResponse))
                        .ifPresent(result -> operation.getCallbackOperator().onResponse(result))));
            return Optional.of(PlatformResponse.builder()
                .responseCode(BleResponseCode.OPERATION_SUCCESS)
                .build());
        }
    },

    NOTIFY_BLE_CHARACTERISTIC_VALUE_CHANGE(1_016) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            return ClientModule.getGattProcessor()
                .map(processor -> operation.getRawRequest()
                    .flatMap(rawRequest -> ClientModule.getParser()
                        .flatMap(parser -> parser.mapTo(rawRequest, CharacteristicNotifyRequest.class)))
                    .map(processor::notifyBleCharacteristicValueChange)
                    .map(resultCode -> PlatformResponse.builder()
                        .responseCode(resultCode)
                        .build())
                    .orElse(PlatformResponse.builder()
                        .responseCode(BleResponseCode.OPERATION_SUCCESS)
                        .build()));
        }
    },

    ON_BLE_CHARACTERISTIC_VALUE_READ(1_017) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            ClientModule.getGattProcessor().ifPresent(processor ->
                processor.onBleCharacteristicValueRead(changeResponse ->
                    ClientModule.getParser()
                        .flatMap(parser -> parser.stringify(changeResponse))
                        .ifPresent(result -> operation.getCallbackOperator().onResponse(result))));
            return Optional.of(PlatformResponse.builder()
                .responseCode(BleResponseCode.OPERATION_SUCCESS)
                .build());
        }
    },

    ON_BLE_CHARACTERISTIC_VALUE_WRITE(1_018) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            ClientModule.getGattProcessor().ifPresent(processor ->
                processor.onBleCharacteristicValueWrite(changeResponse ->
                    ClientModule.getParser()
                        .flatMap(parser -> parser.stringify(changeResponse))
                        .ifPresent(result -> operation.getCallbackOperator().onResponse(result))));
            return Optional.of(PlatformResponse.builder()
                .responseCode(BleResponseCode.OPERATION_SUCCESS)
                .build());
        }
    },

    SET_BLE_CONNECTION_PRIORITY(1_020) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            return ClientModule.getGattProcessor()
                .map(processor -> operation.getRawRequest()
                    .flatMap(rawRequest -> ClientModule.getParser()
                        .flatMap(parser -> parser.mapTo(rawRequest, BleConnectionPriorityRequest.class)))
                .map(processor::setConnectionPriority)
                .map(response -> PlatformResponse.builder()
                    .responseValue(response)
                    .build())
                .orElse(PlatformResponse.builder()
                    .responseCode(BleResponseCode.INVALID_REQUEST_FORMAT)
                    .build()));
        }
    },

    GET_CONNECTED_BLE_DEVICES(1_021) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            return ClientModule.getBleCentralProcessor()
                .map(BleCentralProcessor::getCurrentConnectedDevices)
                .map(connectedDeviceResponse -> PlatformResponse.builder()
                    .responseValue(connectedDeviceResponse)
                    .build());
        }
    },

    READ_BLE_DESCRIPTOR_VALUE(1_023) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            return ClientModule.getGattProcessor()
                .map(processor -> operation.getRawRequest()
                    .flatMap(rawRequest -> ClientModule.getParser()
                        .flatMap(parser -> parser.mapTo(rawRequest, DescriptorReadRequest.class)))
                    .map(processor::readBleDescriptorValue)
                    .map(resultCode -> PlatformResponse.builder()
                        .responseCode(resultCode)
                        .build())
                    .orElse(PlatformResponse.builder()
                        .responseCode(BleResponseCode.OPERATION_FAILED)
                        .build()));
        }
    },

    WRITE_BLE_DESCRIPTOR_VALUE(1_024) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            return ClientModule.getGattProcessor()
                .map(processor -> operation.getRawRequest()
                    .flatMap(rawRequest -> ClientModule.getParser()
                        .flatMap(parser -> parser.mapTo(rawRequest, DescriptorWriteRequest.class)))
                    .map(processor::writeBleDescriptorValue)
                    .map(resultCode -> PlatformResponse.builder()
                        .responseCode(resultCode)
                        .build())
                    .orElse(PlatformResponse.builder()
                        .responseCode(BleResponseCode.OPERATION_FAILED)
                        .build()));
        }
    },

    GET_BLE_CONNECTION_STATE(1_025) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            return Optional.of(PlatformResponse.builder()
                .responseCode(BleResponseCode.OPERATION_FAILED)
                .build());
        }
    },

    SET_BLE_MTU(1_026) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            return ClientModule.getGattProcessor()
                .map(processor -> operation.getRawRequest()
                    .flatMap(rawRequest -> ClientModule.getParser()
                        .flatMap(parser -> parser.mapTo(rawRequest, BleMtuSetRequest.class)))
                    .map(processor::setBleMtu)
                    .map(resultCode -> PlatformResponse.builder()
                        .responseCode(resultCode)
                        .build())
                    .orElse(PlatformResponse.builder()
                        .responseCode(BleResponseCode.OPERATION_FAILED)
                        .build()));
        }
    },

    GET_DEVICE_SERVICES(1_027) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            return ClientModule.getGattProcessor()
                .map(processor -> operation.getRawRequest()
                    .map(processor::getBleDeviceServices)
                    .map(serviceGetResponse -> PlatformResponse.builder()
                        .responseValue(serviceGetResponse)
                        .build())
                    .orElse(PlatformResponse.builder()
                        .responseCode(BleResponseCode.OPERATION_FAILED)
                        .build()));
        }
    },

    GET_DEVICE_CHARACTERISTICS(1_028) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            return ClientModule.getGattProcessor()
                .map(processor -> operation.getRawRequest()
                    .flatMap(rawRequest -> ClientModule.getParser()
                        .flatMap(parser -> parser.mapTo(rawRequest, BleDeviceCharacteristicGetRequest.class)))
                    .map(processor::getBleDeviceCharacteristics)
                    .map(characteristicGetResponse -> PlatformResponse.builder()
                        .responseValue(characteristicGetResponse)
                        .build())
                    .orElse(PlatformResponse.builder()
                        .responseCode(BleResponseCode.OPERATION_FAILED)
                        .build()));
        }
    },

    PAIR_BLUETOOTH_DEVICE(1_029) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            return ClientModule.getBluetoothCentralProcessor()
                .map(processor -> operation.getRawRequest()
                    .map(processor::pairDevice)
                    .map(code -> PlatformResponse.builder()
                        .responseCode(code)
                        .build())
                    .orElse(PlatformResponse.builder()
                        .responseCode(BleResponseCode.OPERATION_FAILED)
                        .build()));
        }
    },

    START_WIFI_SCAN(1_030) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            return ClientModule.getWifiDeviceProcessor()
                .map(WifiDeviceProcessor::startScanning)
                .map(resultCode -> PlatformResponse.builder()
                    .responseCode(resultCode)
                    .build());
        }
    },

    GET_WIFI_STATUS(1_031) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            return ClientModule.getWifiDeviceProcessor()
                .map(WifiDeviceProcessor::getWifiStatus)
                .map(statusResponse -> PlatformResponse.builder()
                    .responseValue(statusResponse)
                    .build());
        }
    },

    GET_CONNECTED_WIFI_INFORMATION(1_032) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            return ClientModule.getWifiDeviceProcessor()
                .map(WifiDeviceProcessor::getConnectedInformation)
                .map(connectionResponse -> PlatformResponse.builder()
                    .responseValue(connectionResponse)
                    .build());
        }
    },

    GET_SCANNED_WIFI_INFORMATION(1_033) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            return ClientModule.getWifiDeviceProcessor()
                .map(WifiDeviceProcessor::getScannedInformation)
                .map(scanningResponse -> PlatformResponse.builder()
                    .responseValue(scanningResponse)
                    .build());
        }
    },

    HTTP_NETWORK_REQUEST(2_001) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            return ClientModule.getHttpProcessor()
                .map(processor -> operation.getRawRequest()
                    .flatMap(rawRequest -> ClientModule.getParser()
                        .flatMap(parser -> parser.mapTo(rawRequest, KitHttpRequest.class)))
                    .map(processor::processHttpRequest)
                    .map(resultCode -> PlatformResponse.builder()
                        .responseCode(resultCode)
                        .build())
                    .orElse(PlatformResponse.builder()
                        .responseCode(HttpResponseCode.OPERATION_FAILED)
                        .build()));
        }
    },

    HTTP_NETWORK_ON_RESPONSE(2_002) {
        @Override
        Optional<PlatformResponse> toProcessRequest(PlatformOperation operation) {
            ClientModule.getHttpProcessor().ifPresent(processor ->
                processor.onHttpResponse(response ->
                    ClientModule.getParser()
                        .flatMap(parser -> parser.stringify(response))
                        .ifPresent(result -> operation.getCallbackOperator().onResponse(result))));
            return Optional.of(PlatformResponse.builder()
                .responseCode(BleResponseCode.OPERATION_SUCCESS)
                .build());
        }
    };

    private final int operationValue;

    PlatformOperationCode(int value) {
        this.operationValue = value;
    }

    /**
     * To get the operation value;
     *
     * @return The integer corresponding to the operation
     */
    public int getOperationValue() {
        return this.operationValue;
    }

    abstract Optional<PlatformResponse> toProcessRequest(PlatformOperation operation);

    /**
     * To process the request from Js FA platform
     *
     * @param remoteOperator The remote operator to reply Js FA response
     * @param request The platform request
     * @return true if such request submit success, false otherwise
     */
    public static boolean processJsFaOperation(JsRemoteOperator remoteOperator, PlatformRequest request) {
        return Arrays.stream(values())
            .filter(operator -> (operator.operationValue == request.getRequestCode()))
            .findAny()
            .flatMap(operator -> operator.toProcessRequest(PlatformOperation.builder()
                .callbackOperator(remoteOperator)
                .platformRequest(request)
                .build()))
            .map(remoteOperator::writeResponse)
            .orElse(false);
    }

    /**
     * To process the general Java request
     *
     * @param operation The platform request operation
     * @return The platform response
     */
    public static PlatformResponse processNativeOperation(PlatformOperation operation) {
        int operationFailed = 1_0001;
        return Arrays.stream(values())
            .filter(operator -> (operator.operationValue == operation.getRequestCode()))
            .findAny()
            .flatMap(operator -> operator.toProcessRequest(operation))
            .orElse(PlatformResponse.builder()
                .resultCode(operationFailed)
                .build());
    }

    /**
     * To process the request from web view platform
     *
     * @param remoteOperator The remote operator to reply web view response
     * @param request The platform request
     * @return The platform request if it is request processed successfully
     */
    public static Optional<PlatformResponse> processH5Operation(
        H5RemoteOperator remoteOperator, PlatformRequest request) {
        return Arrays.stream(values())
            .filter(operator -> (operator.operationValue == request.getRequestCode()))
            .findAny()
            .flatMap(operator -> operator.toProcessRequest(PlatformOperation.builder()
                .platformRequest(request)
                .callbackOperator(remoteOperator)
                .build()));
    }
}
